using System;
using System.Text;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using Microsoft.CommerceServer.Runtime;
using Microsoft.CommerceServer.Runtime.Orders;
using Microsoft.CommerceServer.Runtime.Pipelines;
using Microsoft.CommerceServer.Runtime.Diagnostics;
using Microsoft.CommerceServer.Runtime.Profiles;
using Microsoft.CommerceServer.Runtime.Targeting;
using Microsoft.CommerceServer.Interop.Caching;
using Microsoft.CommerceServer.Orders;

namespace CSharpSite
{
	public class BasicCatalogItemInfo
	{
		private string catalogName;
		private string productId;
		private string variantId;

		public string CatalogName
		{
			get
			{
				return catalogName;
			}
		}

		public string ProductId
		{
			get 
			{
				return productId; 
			}
		}

		public string VariantId
		{
			get 
			{ 
				return variantId; 
			}
		}

		/// <summary>
		/// Creates an instance of BasicCatalogItemInfo.
		/// </summary>
		/// <param name="catalogName">The catalog that the product belongs to.</param>
		/// <param name="productId">The product Id of the product.</param>
		public BasicCatalogItemInfo(string catalogName, string productId)
			: this(catalogName, productId, null)
		{			
		}

		/// <summary>
		/// Creates an instance of BasicCatalogItemInfo.
		/// </summary>
		/// <param name="catalogName">The catalog that the product belongs to.</param>
		/// <param name="productId">The product Id of the product.</param>
		/// <param name="variantId">The variant Id of the product.</param>
		public BasicCatalogItemInfo(string catalogName, string productId, string variantId)
		{
			if (catalogName == null)
			{
				throw new ArgumentNullException("catalogName");
			}
			if (catalogName.Length == 0)
			{
				throw new ArgumentException("catalogName");
			}
			if (productId == null)
			{
				throw new ArgumentNullException("productId");
			}
			if (productId.Length == 0)
			{
				throw new ArgumentException("productId");
			}

			this.catalogName = catalogName;
			this.productId = productId;
			this.variantId = variantId;
		}
	}

	/// <summary>
	/// A helper class that can be used to obtain the discounts that apply to catalog items. 
	/// There are two scenarios where this class can be used:
	/// 1. To obtain a target set of discounts that apply to a given list of products. 
	/// In this case, 
	/// It uses the product pipeline and the Content Selector (which runs the discounts pipeline) to obtain discounts
	/// that are relevant to products that are provided as input. The following Commerce assemblies need to be referenced
	/// by the application in order for this helper class to work:
	///		Microsoft.CommerceServer.Catalog.dll
	///		Microsoft.CommerceServer.Runtime.dll
	///		Microsoft.CommerceServer.Interop.dll
	///		Microsoft.CommerceServer.Orders.CrossTierTypes.dll
	///		Microsoft.CommerceServer.CrossTierTypes.dll
	/// </summary>
	class CatalogItemsHelper
	{
		#region static helper properties
		
		private static PipelineBase ProductPipeline
		{
			get
			{
				PipelineBase productPipeline = CommerceContext.Current.Pipelines["product"];
				if (productPipeline == null)
				{
					throw new InvalidOperationException("The 'product' pipeline must be configured in the 'pipelines' section of the web.config for Targeting of Catalog Items");
				}
				return productPipeline;
			}
		}

		#endregion static helper properties

		#region instance helper properties

		/// <summary>
		/// Creates a context dictionary which is used to execute the Product Pipeline. The dictionary contains the context
		/// properties that are required for the product pipeline.
		/// Of special consideration is the default catalog language (which is the name of the culture information for 
		/// the current thread). 
		/// If the site is not multi-lingual, then the default catalog language needs to be initialized only once, rather than 
		/// on every instance of this class. In this case, the PipelineContextDictionary can be made a static property on this 
		/// class rather than a method. This can improve site performance. 
		/// </summary>
		private static Dictionary CreatePipelineContextDictionary()
		{
			Dictionary _pipelineContextDictionary = new DictionaryClass();
			_pipelineContextDictionary["CommerceResources"] = CommerceContext.Current.Resources;
			_pipelineContextDictionary["CatalogContext"] = CommerceContext.Current.CatalogSystem;
			_pipelineContextDictionary["catalog_language"] = CultureInfo.CurrentCulture.Name;
			return _pipelineContextDictionary;
		}

		#endregion helper properties

		private CatalogItemsHelper()
		{
		}

		#region public methods

		/// <summary>
		/// A helper method for targeting discounts based on line items in a basket.
		/// It translates the required keys from each of the line items in the basket into a dictionary that is then added to
		/// the <see cref="CatalogItemsList"/>. This list can be used later by the <see cref="ContentSelector.GetContent"/> 
		/// method of the <see cref="DiscountsContentSelector"/>. 
		/// This method should be called on the basket after running the basket pipeline (specifically the QueryCatalogInfo 
		/// pipeline component) on the basket. If this component hasn't been run, then most of the Catalog properties for 
		/// the Line Items in the basket will not be loaded and discounts that reference those properties will not be able 
		/// to be targeted.
		/// <code>
		///		ContentSelector cso = CommerceContext.Current.TargetingSystem.SelectionContexts["discounts"].GetSelector();
		///		//set properties on the cso object
		///		//...
		///		//the basket pipeline should already have been run on the basket object
		///		cso["items"] = CatalogItemsHelper.GetProductDetailsFromBasket(basket);
		///		StringCollection basketStrings = cso.GetContent();		
		/// </summary>
		/// <param name="basket">The basket from which the list of lineItems is to be created.</param>
		public static SimpleList GetProductDetailsFromBasket(Basket basket)
		{
			if (basket == null)
			{
				throw new ArgumentNullException("basket");
			}

			SimpleList itemsList = new SimpleListClass();

			foreach (OrderForm orderForm in basket.OrderForms)
			{
				foreach (LineItem lineItem in orderForm.LineItems)
				{
					Dictionary lineItemDictionary = new DictionaryClass();

					//these keys are required for product picker expressions to evaluate correctly
					lineItemDictionary["product_id"] = lineItem.ProductId;
					lineItemDictionary["product_variant_id"] = lineItem.ProductVariantId;
					lineItemDictionary["product_catalog"] = lineItem.ProductCatalog;

					//now copy over the weakly typed properties into the dictionary
					foreach (string key in lineItem)
					{
						lineItemDictionary[key] = lineItem[key];
					}
					object lineItemDictionaryObject = (object)lineItemDictionary;
					itemsList.Add(ref lineItemDictionaryObject);
				}
			}
			return itemsList;
		}

		/// <summary>
		/// Helper method for running the products pipeline. The products pipeline returns details about the products that
		/// are to be targeted. These details are used when scoring discounts that are applicable to the products.
		/// The simple list that is returned by this method can be assigned to the ContentSelector Dictionary key "products"
		/// for targeting of products.
		/// <code>
		///		ContentSelector cso = CommerceContext.Current.TargetingSystem.SelectionContexts["discounts"].GetSelector();		
		///		//set properties on the cso object
		///		//...
		///		cso["products"] =CatalogItemsHelper.GetProductDetailsFromProductPipeline(/*...*/);
		///		cso.GetContent();
		/// </code>
		/// </summary>
		/// <param name="basicCatalogItemInfos">The list of products whose details are to be obtained.</param>
		/// <exception cref="PipelineExecutionException">An error occurs when executing the products pipeline.</exception>
		public static SimpleList GetProductDetailsFromProductPipeline(
			IEnumerable<BasicCatalogItemInfo> basicCatalogItemInfos)
		{
			SimpleList basketErrors;
			int pipelineReturnValue;
			SimpleList catalogItemsList = GetProductDetailsFromProductPipeline(basicCatalogItemInfos, 
				out pipelineReturnValue, out basketErrors);
			PipelineExecutionResult pipelineExecutionResult = (PipelineExecutionResult)pipelineReturnValue;
			if (pipelineExecutionResult == PipelineExecutionResult.Success ||
				pipelineExecutionResult == PipelineExecutionResult.Warning)
			{
				return catalogItemsList;
			}
			else
			{
				StringBuilder errors = new StringBuilder().AppendLine("Errors occurred during pipeline execution.");
				if(basketErrors != null)
				{
					foreach(object error in basketErrors)
					{
						errors.AppendLine(error.ToString());
					}
				}
				Exception innerException = new Exception(errors.ToString());
				throw new PipelineExecutionException("product", innerException);
			}
		}

		/// <summary>
		/// Helper method for running the products pipeline. The products pipeline returns details about the products that
		/// are to be targeted. These details are used when scoring discounts that are applicable to the products.
		/// The simple list that is returned by this method can be assigned to the ContentSelector Dictionary key "products"
		/// for targeting of products.
		/// <code>
		///		ContentSelector cso;
		///		//create an instance and set properties on the cso object
		///		//...
		///		cso["products"] =CatalogItemsHelper.GetProductDetailsFromProductPipeline(/*...*/);
		///		cso.GetContent();
		/// </code>
		/// </summary>
		/// <param name="basicCatalogItemInfos">The list of products whose details are to be obtained.</param>
		/// <param name="pipelineReturnValue">The return value of the product pipeline. This value should be interpreted 
		/// the same way as the return value from <see cref="OrderPipeline.Execute"/>.</param>
		/// <param name="basketErrors">Can be read to obtain details about the errors that may have occurred</param>
		/// <returns></returns>
		public static SimpleList GetProductDetailsFromProductPipeline(
			IEnumerable<BasicCatalogItemInfo> basicCatalogItemInfos,
			out int pipelineReturnValue,
			out SimpleList basketErrors)
		{
			if(basicCatalogItemInfos == null)
			{
				throw new ArgumentNullException("basicCatalogItemInfos");
			}

			SimpleList catalogItemsList = new SimpleListClass();
			basketErrors = new SimpleListClass();

			foreach (BasicCatalogItemInfo basicCatalogItemInfo in basicCatalogItemInfos)
			{
				Dictionary productDictionary = new DictionaryClass();
				productDictionary["product_catalog"] = basicCatalogItemInfo.CatalogName;
				productDictionary["product_id"] = basicCatalogItemInfo.ProductId;
				if (basicCatalogItemInfo.VariantId != null)
				{
					productDictionary["product_variant_id"] = basicCatalogItemInfo.VariantId;
				}
				object productDictionaryObject = productDictionary;
				catalogItemsList.Add(ref productDictionaryObject);
			}

			Dictionary pipelineDictionary = new DictionaryClass();
			pipelineDictionary["_Basket_Errors"] = basketErrors;
			pipelineDictionary["items"] = catalogItemsList;

			pipelineReturnValue = CatalogItemsHelper.ProductPipeline.Execute(pipelineDictionary, CreatePipelineContextDictionary());
			return catalogItemsList;
		}

		#endregion instance public methods
	}

	/// <summary>
	/// Summary description for _Default.
	/// </summary>
	public partial class SamplePage : System.Web.UI.Page
	{
		static ContentSelectionContext DiscountContentSelectionContext
		{
			get
			{
				ContentSelectionContext discountContentSelectionContext = CommerceContext.Current.TargetingSystem.SelectionContexts["discounts"];
				if (discountContentSelectionContext == null)
				{
					throw new InvalidOperationException("The 'discounts' entry must be configured in the 'contentSelection' section of the web.config for Targeting of Catalog Items");
				}
				return discountContentSelectionContext;
			}
		}

		protected void Page_Load(object sender, EventArgs e)
		{
			BasketPageSample();
			ProductPageSample();
		}

		void BasketPageSample()
		{
			PipelineInfo basketPipe = new PipelineInfo("basket");
			LineItem item1 = new LineItem("Adventure Works Catalog", "AW390-12", "1", 2);
			LineItem item2 = new LineItem("Adventure Works Catalog", "AW150-05", null, 2);
			Basket basket = CommerceContext.Current.OrderSystem.GetBasket(Guid.NewGuid(), "basket");
			basket.OrderForms.Add(new OrderForm("one order form"));
			basket.OrderForms[0].LineItems.Add(item1);
			basket.OrderForms[0].LineItems.Add(item2);
			basket.RunPipeline(basketPipe);

			RunCSOWithItemsAndProducts(CatalogItemsHelper.GetProductDetailsFromBasket(basket), null);
		}

		void ProductPageSample()
		{
			//in an actual product page, these product definitions will be obtained from the results of a catalog system search.
			BasicCatalogItemInfo[] productDefinitions = 
			{
					new BasicCatalogItemInfo("Adventure Works Catalog", "AW390-12", "1")
				,	new BasicCatalogItemInfo("Adventure Works Catalog", "AW150-05")
			};
			RunCSOWithItemsAndProducts(null, CatalogItemsHelper.GetProductDetailsFromProductPipeline(productDefinitions));
		}

		void RunCSOWithItemsAndProducts(SimpleList items, SimpleList products)
		{
			ContentSelector cso = DiscountContentSelectionContext.GetSelector();

			if (HttpContext.Current.IsDebuggingEnabled)
			{
				cso.TraceMode = true;
			}

			cso.ItemsRequested = 2;
			if (items != null)
			{
				cso["items"] = items;
			}
			if (products != null)
			{
				cso["products"] = products;
			}

			foreach (string key in cso.GetContent())
			{
				Response.Write("<b>basket display</b>\t");
				Response.Write(key + "<br/>");
			}
			Response.Write("<br/>");

			if (HttpContext.Current.IsDebuggingEnabled)
			{
				foreach (System.Collections.IDictionary contentItem in cso.SelectedContentItems)
				{
					Response.Write("<b>content item</b><br/>");
					foreach (string key in contentItem.Keys)
					{
						Response.Write("  { " + key + "=" + contentItem[key] + " }  ");
					}
					Response.Write("<br/>");
				}

				Response.Write("<br/><br/><b>Traces</b><br/>");
				foreach (StringCollection messages in cso.TraceMessages)
				{
					foreach (string message in messages)
					{
						Response.Write(message + "<br/>");
					}
				}
			}
		}

		#region Web Form Designer generated code
		override protected void OnInit(EventArgs e)
		{
			//
			// CODEGEN: This call is required by the ASP.NET Web Form Designer.
			//
			InitializeComponent();
			base.OnInit(e);
		}
		
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{    
		}
		#endregion
	}
}
